/**
 * Copyright (C) 2010 Cloudfarming <info@cloudfarming.nl>
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.cloudfarming.client.geoviewer.jxmap.map;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import nl.cloudfarming.client.geoviewer.LayerObject;
import nl.cloudfarming.client.geoviewer.ObjectLayer;
import org.jdesktop.swingx.JXMapViewer;
import org.netbeans.api.visual.model.ObjectScene;
import org.netbeans.api.visual.widget.LayerWidget;
import org.netbeans.api.visual.widget.Widget;

/**
 * Layer panel containint objects
 * object layers will be painted as images with a backgroundPainter by default
 * when the layer is activated, the actual objects (widgets) will be painted over the background.
 *
 * @author Timon Veenstra
 */
public class ObjectLayerPanel extends LayerPanel {

    private MouseEventForwarder mouseEventForwarder;
    final ObjectScene scene = new ObjectScene();
    private final JComponent view = scene.createView();
    //
    // widget layer
    private final LayerWidget mainLayer = new LayerWidget(scene);

    public ObjectLayerPanel(final RootMapPanelController controller, final JComponent forwardEventsTo, ObjectLayer layer, JXMapViewer mapViewer) {
        super(controller, forwardEventsTo, layer, mapViewer);

        setLayout(new BorderLayout());
        this.scene.setOpaque(false);
        this.scene.addChild(this.mainLayer);
        this.scene.setMaximumBounds(new Rectangle(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE));

        if (forwardEventsTo != null) {
            this.mouseEventForwarder = new MouseEventForwarder(this.scene, forwardEventsTo);
            this.view.addMouseListener(this.mouseEventForwarder);
            this.view.addMouseMotionListener(this.mouseEventForwarder);
            this.view.addMouseWheelListener(this.mouseEventForwarder);
        }

        layer.addPropertyChangeListener(ObjectLayer.PROPERTY_OBJECTS, new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                List<LayerObject> oldObjects = (List<LayerObject>) evt.getOldValue();
                List<LayerObject> newObjects = (List<LayerObject>) evt.getNewValue();
                //
                // remove objects no longer present in new list
                //
                List<LayerObject> removedObjects = new ArrayList<LayerObject>(oldObjects);
                removedObjects.removeAll(newObjects);
                removeObjects(removedObjects);
                //
                // add new objects
                //
                List<LayerObject> addedObjects = new ArrayList<LayerObject>(newObjects);
                addedObjects.removeAll(oldObjects);
                addObjects(addedObjects);
            }
        });

        addObjects(layer.getObjects());
        add(view);
        view.setVisible(false);
    }

    /**
     * remove objects
     * if remove is called from the event dispatch thread it ill be
     * executed immediate, if not it will be scheduled on the dispatch thread.
     *
     * @param objects
     */
    private void removeObjects(final List<LayerObject> objects) {
        if (EventQueue.isDispatchThread()) {
            internalRemoveObjects(objects);
        } else {
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    internalRemoveObjects(objects);
                }
            });
        }
    }

    private void internalRemoveObjects(List<LayerObject> objects) {
        assert objects != null;
        for (LayerObject layerObject : objects) {
            List<Widget> widgets = this.scene.findWidgets(layerObject);
            if (widgets != null) {
                for (Widget w : widgets) {
                    mainLayer.removeChild(w);
                }
                this.scene.removeObject(layerObject);
            }

        }
        this.scene.validate();
    }

    /**
     * add objects
     * if called from the event dispatch thread it ill be
     * executed immediate, if not it will be scheduled on the dispatch thread.
     *
     * @param objects
     */
    private void addObjects(final List<LayerObject> objects) {
        if (SwingUtilities.isEventDispatchThread()) {
            internalAddObjects(objects);
        } else {
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    internalAddObjects(objects);
                }
            });
        }
    }

    /**
     * Direct add
     *
     * @param nodes
     */
    private void internalAddObjects(final List<LayerObject> objects) {
        assert SwingUtilities.isEventDispatchThread();
        
        Iterator<LayerObject> li = objects.iterator();
        while(li.hasNext()){
            addObject(li.next());
        }
        scene.validate();
    }

    /**
     * Add an object to the panel.
     * this method will add the object to the scene and create a widget attached to it.
     *
     *
     * @param objectNode
     */
    private void addObject(final LayerObject object) {
        assert SwingUtilities.isEventDispatchThread();
        assert ! this.scene.getObjects().contains(object);
        LayerObjectWidget widget = new LayerObjectWidget(this.scene, object, getLayer(), getController());

        widget.getActions().addAction(scene.createObjectHoverAction());
        widget.getActions().addAction(scene.createSelectAction());

        this.mainLayer.addChild(widget);
        this.scene.addObject(object, widget);

    }

    /**
     * activate or deactivate this layer
     * when layer gets activated, the view will be made visible, painting all containing objects as widgets
     * and providing interaction.
     *
     * @param active
     */
    void setActive(boolean active) {
        this.view.setVisible(active);
        setBackgroundPainter(active ? null : super.getPainter());
        this.view.repaint();
    }

    @Override
    public void setSelectedObjects(Set<LayerObject> selectedObjects) {
        this.scene.setSelectedObjects(selectedObjects);
        this.view.repaint();
    }

    @Override
    public void setSelectedObject(LayerObject selectedObject) {
        Set<LayerObject> selectedObjects = new HashSet<LayerObject>();
        selectedObjects.add(selectedObject);
        this.scene.setSelectedObjects(selectedObjects);
        this.view.repaint();
    }
}
